require "option_parser"
require "yaml"
require "./authd.cr"

class Context
	class_property simulation    = false  # do not perform the action

	class_property authd_login   : String? = nil
	class_property authd_pass    : String? = nil

	# # Properties to select what to display when printing a deal.
	# class_property print_title        = true
	# class_property print_description  = true
	# class_property print_owner        = true
	# class_property print_nb_comments  = true

	class_property command       = "not-implemented"

	class_property user_profile  : Hash(String,JSON::Any)?
	class_property email         : String?

	# Will be parsed later, with a specific parser.
	class_property args               : Array(String)? = nil
end

require "./better-parser"

class Actions

	def self.ask_password
		STDOUT << "password: "
		STDOUT << `stty -echo`
		STDOUT.flush
		password = STDIN.gets.try &.chomp

		STDOUT << '\n'
		STDOUT << `stty echo`

		password
	end

	def self.ask_something(str : String) : String?
		STDOUT << "#{str} "
		STDOUT.flush
		answer = STDIN.gets.try &.chomp
		answer
	end


	property the_call = {} of String => Proc(Nil)
	property authd : AuthD::Client

	def initialize(@authd)
		@the_call["user-registration"] = ->user_registration
		@the_call["user-validation"]   = ->user_validation    # Do not require authentication.
		@the_call["user-change-password"] = ->user_change_password
		@the_call["user-recovery"]     = ->user_recovery      # Do not require authentication.
		@the_call["user-delete"]       = ->user_deletion      # Do not require admin priviledges.
		@the_call["user-get"]          = ->user_get
		@the_call["user-search"]       = ->user_search

		@the_call["bootstrap"]         = ->bootstrap

		# Require admin privileges.
		@the_call["user-add"]          = ->user_add
		@the_call["user-migrate"]      = ->user_migrate
		@the_call["migration-script"]  = ->migration_script
		@the_call["user-mod"]          = ->user_mod
		@the_call["exit"]              = ->kill_service

		@the_call["permission-set"]   = ->permission_set
		@the_call["permission-check"] = ->permission_check

	end

	#
	# For all functions: the number of arguments is already tested.
	#

	def user_add
		args = Context.args.not_nil!
		login, email = args[0..1]
		profile = Context.user_profile

		password = Actions.ask_password
		exit 1 unless password

		# By default: not admin.
		pp! authd.add_user login, password.not_nil!, false, email, profile: profile
	rescue e : AuthD::Exception
		puts "error: #{e.message}"
	end

	# Migrate a user from old code base (dnsmanager v1).
	def user_migrate
		args = Context.args.not_nil!
		login, password_hash_brkn = args[0..1]
		profile = Context.user_profile

		pp! authd.migrate_user login, password_hash_brkn
	rescue e : AuthD::Exception
		puts "error: #{e.message}"
	end

	# Migrate a batch of users from dnsmanager v1.
	#
	# Usage: authctl migration-script user-db.txt
	#
	# user-db.txt should be formated as "login <TAB> old-hash".
	def migration_script
		args = Context.args.not_nil!
		filename = args[0]
		profile = Context.user_profile

		File.each_line(filename) do |line|
			login, password_hash_brkn = line.split("\t")
			STDOUT.write ((" " * 150) + "\r").to_slice
			STDOUT.write "adding user '#{login}'\r".to_slice
			response = authd.migrate_user login, password_hash_brkn

			case response
			when AuthD::Response::UserAdded
				pp! response.user
			when AuthD::Response::ErrorMustBeAuthenticated
				Baguette::Log.error "ErrorMustBeAuthenticated"
				exit 1
			when AuthD::Response::ErrorAlreadyUsedLogin
				#Baguette::Log.error "ErrorAlreadyUsedLogin"
			when AuthD::Response::ErrorMailRequired
				Baguette::Log.error "ErrorMailRequired"
			else
				Baguette::Log.error "unknown error"
			end
		end

	rescue e : AuthD::Exception
		puts "error: #{e.message}"
	end

	def user_registration
		args = Context.args.not_nil!
		login, email = args[0..1]
		profile = Context.user_profile

		password = Actions.ask_password
		unless password
			Baguette::Log.error "no password!"
			exit 1
		end

		res = authd.register login, password.not_nil!, email, profile: profile
		case res
		when Response::UserAdded
			Baguette::Log.info "user registered, mail sent"
			exit 0
		when Response::ErrorRegistrationsClosed
			Baguette::Log.error "registrations are closed (only admins can add users)"
			exit 1
		when Response::ErrorAlreadyUsedLogin
			Baguette::Log.error "login already used"
			exit 1
		when Response::ErrorMailRequired
			Baguette::Log.error "an email address is required"
			exit 1
		when Response::ErrorInvalidEmailFormat
			Baguette::Log.error "provided email address has an invalid format"
			exit 1
		when Response::ErrorCannotContactUser
			Baguette::Log.error "an error occured while contacting the user with this email address"
			exit 1
		when Response::ErrorInvalidLoginFormat
			Baguette::Log.error "invalid login"
			exit 1
		when Response::ErrorPasswordTooShort
			Baguette::Log.error "password too short"
			exit 1
		end
	rescue e
		puts "error: #{e.message}"
	end

	def bootstrap
		puts "Bootstrap"
		args = Context.args.not_nil!
		login, email = args[0..1]
		profile = Context.user_profile

		password = Actions.ask_password
		exit 1 unless password

		pp! authd.bootstrap login, password.not_nil!, email, profile
	rescue e : AuthD::Exception
		puts "error: #{e.message}"
	end

	def kill_service
		puts "Kill the service."
		authd.exit
	rescue e : AuthD::Exception
		puts "error: #{e.message}"
	end


	def user_change_password
		args = Context.args.not_nil!
		login = args[0]

		password : String? = nil
		Baguette::Log.info "new password:"
		password = Actions.ask_password
		exit 1 unless password

		res = authd.mod_user login, password #, email, admin
		puts res
	end

	# TODO
	def user_mod
		args = Context.args.not_nil!
		userid = args[0]

		password : String? = nil

		should_ask_password = Actions.ask_something "Should we change the password (Yn) ?" || "n"
		case should_ask_password
		when /y/i
			Baguette::Log.debug "Ok let's change the password!"
			password = Actions.ask_password
			exit 1 unless password
		else
			Baguette::Log.debug "Ok no change in password."
		end

		email = Context.email

		Baguette::Log.error "This function shouldn't be used for now."
		Baguette::Log.error "It is way too cumbersome."

        # res = authd.mod_user login, password, email, admin
        # puts res
	end

	def user_deletion
		args = Context.args.not_nil!
		userid = args[0].to_u32

		res = authd.delete userid

		puts res
	end

	def user_validation
		args = Context.args.not_nil!
		login, activation_key = args[0..1]
		pp! authd.validate_user login, activation_key
	end
	def user_search
		args = Context.args.not_nil!
		login = args[0]
        pp! authd.search_user login
	end
	def user_get
		args = Context.args.not_nil!
		login = args[0]
		pp! authd.get_user? login
	end
	def user_recovery
		args = Context.args.not_nil!
		login = args[0]
        pp! authd.ask_password_recovery login
	end

	def permission_check
		args = Context.args.not_nil!
		user, application, resource = args[0..2]
		res = @authd.check_permission user.to_u32, application, resource
		case res
		when Response::PermissionCheck
			s = res.service
			r = res.resource
			u = res.user
			p = res.permission
			Baguette::Log.info "app #{s} resource #{r} user #{u}: #{p}"
		end
	end

	def permission_set
		args = Context.args.not_nil!
		user, application, resource, permission = args[0..3]
		perm = AuthD::User::PermissionLevel.parse(permission)
		res = @authd.set_permission user.to_u32, application, resource, perm
		case res
		when Response::PermissionSet
			s = res.service
			r = res.resource
			u = res.user
			p = res.permission
			Baguette::Log.info "app #{s} resource #{r} user #{u}: #{p}"
		end
	end
end

def main

	# Authd connection.
	authd = AuthD::Client.new

	if login = Context.authd_login
		pass = if p = Context.authd_pass
			p
		else
			password = Actions.ask_password
			raise "cannot get a password" unless password
			password
		end
		response = authd.login? login, pass
		case response
		when Response::Login
			uid = response.uid
			token = response.token
			Baguette::Log.info "Authenticated as #{login} #{uid}, token: #{token}"
		else
			raise "Cannot authenticate to authd with login #{login}: #{response}."
		end
	end

	actions = Actions.new authd

	# Now we did read the intent, we should proceed doing what was asked.
	begin
		actions.the_call[Context.command].call
	rescue e
		Baguette::Log.info "The command is not recognized (or implemented)."
		Baguette::Log.info "Exception: #{e}."
		pp! e
	end

	# authd disconnection
	authd.close
rescue e
	Baguette::Log.info "Exception: #{e}"
end


# Command line:
#   tool [options] command [options-for-command]

main
